function mxKeyHandler(graph, target)
{
	if (graph != null)
	{
		this.graph = graph;
		this.target = target || document.documentElement;
		this.normalKeys = [];
		this.controlKeys = [];
		var self = this;
		mxEvent.addListener(this.target, "keydown", function(evt)
		{
			self.keyDown(evt);
		});

		if (mxClient.IS_IE)
		{
			mxEvent.addListener(window, 'unload', function()
			{
				self.destroy();
			});
		}
	}
};
mxKeyHandler.prototype.graph = null;
mxKeyHandler.prototype.target = null;
mxKeyHandler.prototype.normalKeys = null;
mxKeyHandler.prototype.controlKeys = null;
mxKeyHandler.prototype.enabled = true;
mxKeyHandler.prototype.isEnabled = function()
{
	return this.enabled;
};
mxKeyHandler.prototype.setEnabled = function(enabled)
{
	this.enabled = enabled;
};
mxKeyHandler.prototype.bindKey = function(code, funct)
{
	this.normalKeys[code] = funct;
};
mxKeyHandler.prototype.bindControlKey = function(code, funct)
{
	this.controlKeys[code] = funct;
};
mxKeyHandler.prototype.getFunction = function(evt)
{
	if (evt != null)
	{
		return (mxEvent.isControlDown(evt)) ? this.controlKeys[evt.keyCode] : this.normalKeys[evt.keyCode];
	}
	return null;
};
mxKeyHandler.prototype.isGraphEvent = function(evt)
{
	var source = mxEvent.getSource(evt);

	if ((source == this.target || source.parentNode == this.target)
		|| (this.graph.cellEditor != null && source == this.graph.cellEditor.textarea))
	{
		return true;
	}
	var elt = source;

	while (elt != null)
	{
		if (elt == this.graph.container)
		{
			return true;
		}
		elt = elt.parentNode;
	}
	return false;
};
mxKeyHandler.prototype.keyDown = function(evt)
{
	if (this.graph.isEnabled() && this.isGraphEvent(evt) && this.isEnabled())
	{
		if (this.graph.isEditing() && ((evt.keyCode == 13 && !mxEvent.isControlDown(evt) && !mxEvent.isShiftDown(evt))
			|| (evt.keyCode == 113)))
		{
			this.enter(evt);
		}
		else if (evt.keyCode == 27)
		{
			this.escape(evt);
		}
		else if (!this.graph.isEditing())
		{
			var boundFunction = this.getFunction(evt);

			if (boundFunction != null)
			{
				boundFunction(evt);
				mxEvent.consume(evt);
			}
		}
	}
};
mxKeyHandler.prototype.enter = function(evt)
{
	if (this.graph.isEnterStopsCellEditing)
	{
		this.graph.stopEditing(false);
	}
};
mxKeyHandler.prototype.escape = function(evt)
{
	if (this.graph.isEscapeEnabled())
	{
		this.graph.escape(evt);
	}
};
mxKeyHandler.prototype.destroy = function()
{
	this.target = null;
};